CraigMattson.net

MediatR vs. Minimal APIs vs. FastEndpoints

25 September 2022

In the world of .NET-based microservice development, no doubt you have come across the de-facto standard of all-things-CQRS - MediatR. It has been a great library to work with when your application stack contains multiple entry points to the same unit of code, and you really don't want to spend the time writing boilerplate reflection code to achieve similar outcomes. In some respects, it's become a bit of a hammer for me - download the library, add the references and start writing your request / response objects just as I've been writing since the first "simple" WCF replacements became available for REST. It's hard to move away from the simplicity of treating things as Requests, using some scaffolding to find the right handler and produce a Response. It's simple, it follows the way HTTP works, there's just not a lot to think about.

In .NET 6, there's been a push by Microsoft to start introducing minimal code. I was rudely introduced to this in a Console App I launched a few months ago when old mates public static void Main(string args[]) and using System; were all missing from my code. No namespace, no nothing. Just an empty screen with Console.WriteLine("Hello World");. Delving a little further, it appears that ASP.Net wasn't immune from the same smack-in-the-face change either. Just 4 lines of boilerplate code to instantiate your website removing slabs of Startup.cs and Program.cs code.

Embracing this new life of simplicity inching ever-so-much closer to the conveniences of Python (Fast) and NodeJS (expressjs), .NET 6 and a reasonably newcomer to the platform FastEndpoints might well become the second and third hammers I need to take the stickiness out of several layers of abstraction that MediatR and MVC usually requires before you can use it as intended.

A brief look at Minimal APIs

When you create your new empty ASP.Net Core website, you'll be greeted with a new option "Do not use top-level statements". This option removes the boilerplate / scaffolding code for using statements, Program.cs, Startup.cs and lets you get down to business almost immediately.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

This is significantly less code than how you'd do this in MVC. I won't put an example here, but the above demonstrates the following:

  • Minimal APIs is initialised by default (there's no code here to activate MVC Controllers or Routes).
  • Routes are explicitly defined against their appropriate MapVerb (and thus not implied due to naming conventions or altered via Attributes).
  • No Namespaces or Class Decorators - your code should run a lot faster than initiating large Controller classes.

Getting started is simple - create some DTO objects to match your requests and responses, write minimal code here and gradually update them as you go on. If you're really embracing the microservices design, you might even find that a single Program.cs is enough for your few data manipulation services. Getting more complicated? Dependency Injection is still available, as is reflection or simple instantiation of your own abstractions will help you get there.

You can still instantiate controllers and use them where appropriate, but when working with APIs, I've never found API Controllers to be particularly intuitive. There's a lot going on simply to handle authorisation and routing, and if you're embracing MediatR / CQRS patterns - you're controllers become nothing more than conduits for mediator Send calls.

What is FastEndpoints?

FastEndpoints is the not-so-new kid on the block embracing the concept of Minimal APIs by allowing you to replace those MVC Controllers with Endpoints. When paired with "Do not use top-level statements", instantiation is super simple. You'll first need a couple of packages:

With those installed, we add the scaffolding and we're ready to write our first Endpoint.

global using FastEndpoints;
global using FastEndpoints.Swagger;

// do the services thing here
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints();
builder.Services.AddSwaggerDoc();
// including things like your DI

var app = builder.Build();

// do the app thing here
app.UseAuthorization();
app.UseFastEndpoints();
app.UseOpenApi();
app.UseSwaggerUi3(s => s.ConfigureDefaults());

// start!
app.Run();

Instead of the Minimal APIs approach with app.MapGet("/"), you create a typical class extending one of the following base classes:-

  • Endpoint<TRequest> or Endpoint<TRequest, TResponse>
  • EndpointWithoutRequest or EndpointWithoutRequest<TResponse>

Your TRequest and TResponse objects are Plain Old Class Objects. With this in mind, you can start scaffolding your class.

// usings for your POCO's

namespace App.Endpoints
{
    public class UpdatePocoEndpoint : Endpoint<PocoRequest, PocoResponse>
    {
        public override void Configure()
        {
            AllowAnonymous();
            Post("/poco");
            Summary(s =>
            {
                // put your swagger configurations here
                // per FastEndpoints.Swagger
            });
        }

        public override Task HandleAsync(PocoRequest req, CancellationToken ct)
        {
            // do something to update your Poco
            return SendOkAsync(new PocoResponse(), ct);
        }
    }
}

The first thing you notice is the Configure() method - this creates a semantic way of declaring the configuration. These also ways to use Attributes to achieve the same thing. More importantly, there's no expensive MVC framework, DI to be instantiated for all 'routes' and really - you're just focussing on your code.

If you're building simple request / response style APIs, give this a try - you'll remove a lot of unnecessary code while still supporting Dependency Injection through either your Resolve extensions or via your constructor as you do today.

For more details, see the comprehensive documentation on FastEndpoints.

What about MediatR?

While it's not the cleanest pattern out there, I'm a fan of MediatR. While it masquerades as the original Gang of Four 'Mediator' pattern, it  really is a glorified service locator pattern useful for mapping a series of requests and responses. In doing so, it's a useful receptacle for both Request / Response and CQRS style APIs when coupled with ASP.Net MVC Web APIs where your ApiControllers masquerade as some kind of gateway.

Once you add your MediatR library (and DependencyInjection component) via NuGet to your ASP.Net MVC Web API project, you then add MediatR to your ConfigureServices method in Startup.cs

services.AddMediatR(typeof(Startup));

In a roundabout way, MediatR extracts the executing assembly from your injected Type, and begins the hunt for things that implement: 

  • IRequest
  • IRequest<TResponse>
  • IRequestHandler<TRequest, TResponse>

That means you'll need a few components:

// queries - specify your repsonse type
public class PocoQuery : IRequest<Poco> {
    // stuff for your handler request
}

// commands (if dealing with CQRS, you don't want to return anything
public class AddPocoCommand : IRequest {
    // add your properties here    
}

// query handlers
public class PocoQueryHandler : IRequestHandler<PocoQuery, Poco> {
    // add your di and constructors

    public async Task<Poco> Handle(PocoQuery query, CancellationToken cancellationToken) {
        // return a Poco here.
    }
}

// command handlers
public class AddPocoCommandHandler : IRequestHandler<AddPocoCommand> {
    // add your di and constructors

    public async Task Handle(AddPocoCommand command, CancellationToken cancellationToken) {
        // do your writes and publishes here.
    }
}

This is where the "magic" reveals itself. All of those interfaces that you extend - and that's assuming you don't go further at creating base classes to mimic "Command" and "Query" nomenclature (e.g. public interface ICommand : IRequest { ... } and so forth). I don't mind it personally because it moves the 'mess' of Dependency Injection over to your commands - but it doesn't make it very portable, rather it tightly couples your library to the MediatR framework. Again - this is a tradeoff. If you are happy to embed MediatR into your application then you should make full use of it. It is unit testable and it can make things logically cleaner. But it's not magic.

It also makes code discoverability very tricky without third party plugins, or an aptitude in "ctrl+," exploring via naming conventions. You can also go searching for 'references in other locations' - it's still not as simple as finding the concrete implementation of an interface as you would with Dependency Injection.

Why not MediatR?

Some arguments for using MediatR is not having to write your own handlers or lists, but being totally honest - a competent developer will hammer that out as part of scaffolding. It's not an overly complicated task, and making architecture decisions like that means you need to be totally on-board with what the library offers. If you were to write your own Query Handler interface, one may assume it could look as simple as:

// query handler
public interface IQueryHandler<TQuery, TResult> {
    public abstract Task<TResult> ExecuteAsync(TQuery query, CancellationToken token);
}

// command handler
public interface ICommandHandler<TCommand> {
    public abstract Task ExecuteAsync(TCommand command, CancellationToken token);
}

You then register these in your Dependency Injection framework as follows:

// in your Startup.cs
services.AddScope<IQueryHandler<MyQuery, MyQueryResult>, QueryHandler<MyQuery, MyQueryResult>>();

// in your constructor
public class SomeWebController : ApiController {
    readonly IQueryHandler<MyQuery, MyQueryResult> handler;

    public SomeWebController(IQueryHandler<MyQuery, MyQueryResult>) { ... }
}

You can certainly see why MediatR can seem nice at first - there's less "stuff" going on, however all those handler registrations - all those interfaces you add to your commands and queries, and pointless abstractions on-top mean you end up writing the same amount of code anyway - and you don't get the same level of visibility.

If you don't want to specify all those types, you can proceed to add extras to your interfaces, and then not bind service locator patterns in your code.

If you want to automatically find all of your query handlers, then reflection can help you write a method to do exactly that - just as the MediatR "typeof(Startup)" component works.

That's where FastEndpoints may have an advantage here. Going back to why we use MediatR in our Web APIs, might have something to do with the way routing in MVC works. You tend to add lots of entrypoints - kind of like an API Gateway endpoint meaning your constructor could be totally bloated with handlers for each of your methods. That would be undesirable and messy.

However, if FastEndpoints allows you to specify your routing path (without having to instantiate full ApiControllers and adding lots of attributes to change route paths), then your DI spam will be reduced. In fact, if you need to share code or write directly into your endpoint - you'll find less lines of code and better discoverability to achieve the same.

Will I stop using MediatR? Yes - I've stopped using it for my personal projects and switched to FastEndpoints. Those also have different use cases though (e.g. if you make a Christmas Light controller - you have ~15 endpoints - not ~1,500). Either way, I'm looking forward to what Minimal APIs has to offer in upcoming releases and how much Microsoft may borrow from projects like FastEndpoints to create that experience enjoyed by node.js and Python web developers in 2022.


Building a Synchronised Christmas Lights to Music Display - Part 7

05 January 2022

With Christmas now over, the lights have come down and apart from what's left on the roof - it's packed down fairly easily. The show itself ran until the 28th December 2021. I originally planned for it to run right to the end of the year but with no viewers after Christmas Day, it was time to turn it off.

Suffice to say, the actual show was pretty uneventful (in a good way!). No police called, no neighbour complaints, just a lot of happy people coming by in groups - 1-2 an hour. I didn't advertise the display but it was organically found within the first hour of the first show and posted on the local Facebook groups. No further posts up until the last few days leading up to Christmas itself. Suffice to say if I had posted there, there'd certainly have been a lot of visitors which no doubt would have started to upset those in the street and attract the attention of those wanting to impose restrictions on lights. There are plenty of stories in the community this year about councils cracking down on large gatherings requiring some displays to register as 'events' and have traffic management plans in place so I'm glad I didn't have to face that this year.

None-the-less, with a successful light display for 2021, I thought I'd catch up on the few things that occurred during the show of notable interest.

Helping the viewers know what it was they were looking at

As it turns out, not many people knew the display was synchronised to music. Furthermore, after around a minute in the first week - people would drive off - presumably not seeing anything that would indicate that there is music here, even though each sequence would display the FM frequency to tune into and intermissions for the same. I thought the singing trees themselves may have been enough of a hook to understand that something more is going on - but alas, no. So after a night of coffee and code, I'd put together a poster and a web app that would enable viewers to see the schedule, see what's playing, control the display by selecting something to play and allow them to boost a small speaker I'd place in the letterbox for the rest of the run.

Figure 1 - Web App Playlist

The concept was simple. Falcon Player has a full REST API available, as well as the ability to intercept UDP messages for the Multisync protocol - which tells the other "slave" players what's playing and what timestamp it's up to. I would build a basic poller with cache that would talk to both the Speaker Pi and the main Falcon Player Controller - but also listen to the Multisync broadcasts for immediate changes in status, and time position (otherwise, you've got to start writing 1-2 second pollers). To do this, I'd set up a VPN between my home router (a Ubiquiti Dream Machine Pro) and the web server. This created some sort of proxy that meant any damage to the show via the web page would not impact my home network.

The Web App on request would ask the poller for it's status, playlist and allow requests for a temporary volume increase to make it's way through to the Speaker Pi specifically. The frontend is a simple Angular SPA - nothing fancy going on here. Just a Flex Layout with a tabbed style interface and some material design popups. A poster would be visible on the letterbox with a large QR code to access the webpage. For future years, I'd like to incorporate some sort of code to flash up that you key in to confirm you're actually standing in front of the house to avoid abuse, but for this year it was sufficient to just have a kill switch if need be to turn off requests or speaker boosts. I'm also in favour of not putting hurdles in front of people, unless I need to due to exploitation of whatever is displayed.

Figure 2 - Web App Diagram

The speaker setup used a Romoss 30,000mAh battery, a Raspberry Pi 2, a TP-Link AC750 router in Client Bridge mode and an old but fairly good quality portable speaker. I had also 3D Printed a button to stick on the letter box, but very few used it so it was removed after a week - it really needed to be incorporated into a prop or something else interactive I think - and perhaps with Covid-19 fresh in everyone's mind, perhaps people didn't want to press the button just in case. The first time I had set it up, for a few days the player would randomly freeze. I thought it might have been low power or the fact this Pi is probably 6+ years old. I also tried a Pi Zero - but having to add USB soundcards made that impractical and they'd just stop responding to Multisync packets. It turns out that after a bit of investigation of the system logs, that the SD card might be faulty. Swapping the SD card seemed to fix the issue, not sure if the SD card itself was faulty or what...

The sound quality itself was fine, but you could tell that there was a fair bit of "catch up" when using wireless to get the best 'sync' possible. I don't have a particularly strong wireless access point near the front of house meaning the wireless strength was pretty poor itself no doubt contributing to the problem. It also highlighted why you probably don't want to run your show from wireless only as you might find your lights slightly delayed and 'choppy' too.

With Covid-19 restrictions requiring QR code scanning, people are so used to scanning QR codes of any kind with their phones. With a large enough one on a poster, people will get out of their cars to scan it. This improved the viewer rate, and the kids that you could hear in the car certainly seemed to enjoy it more when the Bluey remixes and Polar Express were available. Overall, a win for having an app that viewers can select and play what they want to hear on the display.

Fast repairs - and boy you're going to want to practice this.

At the end of the day, the display is all DIY. From time to time you'll mess up a solder joint, or maybe you'll not put enough silicone in to make the string of LEDs waterproof. The first failure I had was all down to a bad solder joint from possibly the very first LED strip I put together. The symptoms were that it'd stop working - especially after a bit of light rain. A bit of a wiggle would bring it back to life - but of course it had to be one of the ones that were on the roof.

Other failures included:

  • One particular snowflake that had multiple bulb failures - this is where you will come well acquainted with scotch locks and other joiners. You'll probably also want to buy extra strip or strings to combat any failures you might have during the show.
  • In the small matrix, one of the bulbs lost colour intermittently - this wasn't worth the hassle of cutting it out as the Data line was being passed through fine.
  • In the larger matrix, one pixel had caused significant flashing - I didn't have a spare for this kind of string so it remained flashing during the show.
  • The FM transmitter I bought could barely make a couple of meters so I needed a replacement that would at least get a 20m range. I'm sure the original one could have been modified to go further but alas - fighting with it in the first week wasn't pleasant.

What people really like in a display.

When some general public viewers would rock up while I was outside viewing / recording / anything really, most would start with some shock with what they were seeing (i.e. all new), eventually realise it was synchronised to music and then they would proceed to talk about others in the area. The noticeboards and other local groups on Facebook also indicated the same. It would appear that the more lights there are, the better - but they also like props and given the sheer number of them this year (including the next door neighbours inflatables display), it's clear that despite it being unique that you do tend to come across those who think you can buy the stuff straight out of Bunnings.

What I take away from this is that the general public love lights and displays. They don't think too much about it (the occasional one does), and they will move on to the next and comment later about how awesome entire streets are when everyone's working together. And that's really it. If you're in a street with lots of lights, your street becomes the talk of the town - and you get your driveways clogged with cars just as another local street does on a routine basis.

To that end, whatever you make - be it singing, plain, inflatables, people will generally like it if the lights light up. The downside to a fully flashing display is that I felt that I needed to turn it off after 10PM - at least switch it to a non-blinking display. It'd would be too distracting if it was blasting in your window or down the street.

What to do for 2022's display?

I get asked this one a lot. I'm still not sure what I plan on doing, but I'm sure it'll involve more focus around props. People really love the props that move and so I might even look into waterproofing up some stepper motors and giving that all a go. Not sure how that will pan out, but hey - check back here in September and I might have more of an idea.

If you're thinking about doing a display yourself though for the first time, I'd recommend you grab yourself some pen and paper and start drawing out a layout. Pixels will go on sale in the next month or two and paying for shipping by boat will be far cheaper than any airfares you might have to pay otherwise. I'll probably be ordering several thousand pixels soon(tm) even if I don't fully know what I'm going to do with them ... yet.

In the meantime, this will be the last post on the Christmas Lights display for 2021 but there are more 3D printing projects coming up. So much for this being a coding blog hey.


« < 1 > »